【Laravel】簡単に最新 or 最高データを連結できる「One of Many 」

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、まだまだ私も未熟なエンジニアですので日頃から優秀な方々からの情報チェックはかかさないようにしています。

そして、そんな中から久しぶりに、

「おっ、これは❗」

と思うものを発見しました。

それが・・・・・・

One of Many を使ったリレーションシップ

です。

これは例えば、「Aさんのユーザー情報」と「Aさんが投稿した記事の中から最新のものだけ」を一緒に取得できる機能です。

(例)

  • id: 1(←ユーザーテーブルから)
  • name: Aさん(←ユーザーテーブルから)
  • email: a@example.com(←ユーザーテーブルから)
  • latest_post: (タイトルなど)(←記事テーブルから最新のものだけ

つまり、シンプルに言うと「1:多(hasMany)のデータの中から最新のものだけとってくる」バージョンと考えてください。

そこで❗

今回はリレーションシップから「最も新しい(古い)のもの」「最も大きい(小さい)もの」を簡単に取ってくることができる「One of Many」連結をご紹介します。

何かの参考になりましたら嬉しいです。😄✨


「道でバッタリ会った
同級生が、代表取締役してて
ビックリしました 😳」

開発環境: Laravel 8.79.0

One of Many 連結について

One of Manyは、Laravel 8.42で追加された新機能です。
そのため、Laravel 8.xを使っていても対応していない場合がありますので、ご注意ください。

なお、Laravelのアップデートは以下のコマンドを実行するだけでOKです。

composer update

この記事の趣旨

One of Manyはそれほど最近追加されたものではない(2021.5.18)ですし、詳しい内容は他のサイトでも紹介されているのでこの記事では実際にどうやればOne of Manyが使えるかを順を追ってご紹介したいと思います。

必要なファイルをつくる

では、まずはDBまわりのファイルをつくります。
作成するのは「生徒」&「(生徒の)テスト結果」です。

つまり、必要なテーブルは以下のとおりです。

  • students: 生徒データ
  • examinations: 過去に受けたテストの結果(生徒ごとに複数OK)

では、これら必要なファイルをつくるために以下のコマンドを実行してください。

php artisan make:model Student -ms
php artisan make:model Examination -ms

すると、「マイグレーション」「モデル」「Seeder」が2セット(合計6ファイル)が作成されます。

マイグレーションをつくる

では、マイグレーションを以下のように変更してください。

database/migrations/****_**_**_******_create_students_table.php

// 省略

public function up()
{
    Schema::create('students', function (Blueprint $table) {
        $table->id();
        $table->string('name')->comment('生徒名');
        $table->timestamps();
    });
}

database/migrations/****_**_**_******_create_examinations_table.php

// 省略

public function up()
{
    Schema::create('examinations', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('student_id')->comment('生徒ID');
        $table->unsignedInteger('score')->comment('点数');
        $table->timestamps();

        $table->foreign('student_id')->references('id')->on('students')->onDelete('cascade');
    });
}

Seeder でテストデータをつくる

続いて、テストデータをstudentsexaminationsテーブルに用意するためのSeederファイルをつくります。

まずは生徒データです。

database/seeders/StudentSeeder.php

// 省略

public function run()
{
    $names = [
        '山田太郎',
        '佐藤次郎',
        '田中三郎',
        '山本四郎',
        '藤原五郎',
    ];

    foreach ($names as $name) {

        $student = new Student();
        $student->fill(['name' => $name]);
        $student->save();
        
    }
}

そして、テスト結果のデータです。

database/seeders/ExaminationSeeder.php

public function run()
{
    $students = Student::get();

    foreach ($students as $student) {

        for($i = 0 ; $i < 10 ; $i++) {

            $dt = now()->subDays(rand(1, 365)); // 1 〜 365日前の日付

            $examination = new Examination();
            $examination->fill([
                'student_id' => $student->id,
                'score' => rand(0, 100),
                'created_at' => $dt,
                'updated_at' => $dt,
            ]);
            $examination->save();

        }

    }
}

※ ちなみに今回はfill()を使ってデータをセットしてみました。
Laravelは保存だけ見てもいろんなやりかたがあるので、ぜひ自分に合ったものを見つけてください👍

Seederファイルは作成しただけでは実行されないので、DatabaseSeeder.phpにセットします。

database/seeders/DatabaseSeeder.php

class DatabaseSeeder extends Seeder
{
    // 省略

    public function run()
    {
        // 省略

        $this->call(StudentSeeder::class);
        $this->call(ExaminationSeeder::class);
    }
}

では、以下のコマンドでDBを再構築してください。

php artisan migrate:fresh --seed

すると、実際のテーブルはこうなりました。

モデルを変更する

では、ここからが今回の本題「One of Many」連結です。
ひとつずつご紹介しましょう。

※ なお今回は分かりやすいように、「山田太郎」さんだけにフォーカスしてご紹介しますが、その他でも同じです。

山田太郎」さんの実際のデータは次のとおりです。

最新のデータと一緒に取得する

では、まずは「山田太郎さん」と、「山田太郎さんが一番最後に受けた最新テストデータ」を一緒に取得する例を見てみましょう。

app/Models/Student.php

// 省略

class Student extends Model
{
    // 省略

    // Relationship
    public function latest_examination() // 一番新しいテスト結果
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->latestOfMany();
    }

このように、hasOne()に続けてlatestOfMany()を呼ぶだけでOKです。

そして、実行するのは通常どおりリレーションシップと一緒にデータ取得します。

$student = \App\Models\Student::with('latest_examination')->find(1);

dd($student->toArray());

これを実行すると以下のようになります。

はい❗
このように「山田太郎」さんのテスト結果の中で「一番 ID番号 が大きいデータ」が一緒に取得できました。

⚠ ここで注意が必要なのですが、通常は「ID が一番大きい = 最新データ」となりますが、今回はデータをランダムに用意したこともあって最新の日時は、ID: 9の「2021-12-06 03:39:41」のものです。

もしこんな場合は、次のようにターゲットになるカラム名をセットすればOKです。

app/Models/Student.php

class Student extends Model
{
    // 省略

    // Relationship
    public function latest_examination_date_time() // 一番新しいテスト結果(日時)
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->latestOfMany('created_at'); // 👈 カラム名をセットする
    }

そして、呼び出し方も先ほどと同じようにします。

$student = \App\Models\Student::with('latest_examination_date_time')->find(1);

すると、実際の結果はこうなりました。

先ほどはID: 10でしたが、今回は最新日時のデータをもったID: 9になっています。

めちゃくちゃ便利ですね👍

最古のデータと一緒に取得する

一番古いデータと一緒にデータ取得するのは、先ほどのlatestOfMany()とほぼ同じなので省略してご紹介します。

app/Models/Student.php

// 省略

class Student extends Model
{
    // 省略

    // Relationship
    public function oldest_examination() // 一番古いテスト結果
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->oldestOfMany();
    }

    public function oldest_examination_date_time() // 一番古いテスト結果(日時)
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->oldestOfMany('created_at');
    }

このように、latestOfMany()oldestOfMany()に変更になっただけでOKです。こちらも簡単ですね👍

そして、呼び出し方は次のとおりです。

// ID ベース
$student = \App\Models\Student::with('oldest_examination')->find(1);

// 日時ベース
$student = \App\Models\Student::with('oldest_examination_date_time')->find(1);

最高得点データと一緒に取得する

山田太郎さん」が過去に受けたテストの中で一番高い得点のデータと一緒に取得する場合です。

つまり、今回のデータで言うと86点が取得できれば成功です。

やり方はこうなります。

app/Models/Student.php

class Student extends Model
{
    // 省略

    // Relationship
    public function highest_score() // 最高得点
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->ofMany('score', 'max');
    }

先ほどと似ていますが、今回はofMany()を使って、ターゲットになるカラム名scoremax(最大)をセットしています。

そして、今回も通常通りリレーションシップを呼び出すだけでOKです。

$student = \App\Models\Student::with('highest_score')->find(1);

実際のデータはこうなります。

はい❗
最高得点の86点が取得できました。

(なお、ホントはサンプルとして違うIDのデータの方が良かったのですが、ランダムデータの運が悪かったです…😥)

最低得点データと一緒に取得する

勘がいい方ならもうおわかりかもしれませんが、さっきのmaxminに変更するだけです。

// 省略

class Student extends Model
{
    // 省略

    // Relationship
    public function lowest_score() // 最低得点
    {
        return $this->hasOne(Examination::class, 'student_id', 'id')->ofMany('score', 'min');
    }

使い方も同じで次のようになります。

$student = \App\Models\Student::with('lowest_score')->find(1);

実際の結果はこうなります。

データはこちら。

企業様へのご提案

今回ご紹介した内容のように「より効率的なコード」を書くようにすると、開発スピードを上げたり、メンテンナンスしやすくすることができます。

もしそういった開発をご希望でしたら、お問い合わせからぜひご依頼ください。

お待ちしております。m(_ _)m

おわり

ということで、今回はLaravelの(ちょっとだけ)新しい機能「One of Many」をサンプルを使ってご紹介しました。

これは個人的にですが、プログラムも法律も「知っている人が得をする(≒ 知らない人はソンをする)」世界ですので、完璧に覚えておく必要はないですが「あ、そういえばラクできるんじゃなかったけ?? 検索 検索❗」ぐらいにしておいて損することはないと思います。

ぜひ皆さんも、サンプルを使って便利さを体感してみてくださいね。

ではでは〜❗

「ピアノの弾き方を変えたら
ミスタッチだらけ…
YouTuberとかスゴすぎます😣」

このエントリーをはてなブックマークに追加       follow us in feedly  
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ